package com.hqd.ch03.v35.web.method.annotation;

import com.hqd.ch03.v35.beans.converter.ConversionService;
import com.hqd.ch03.v35.core.MethodParameter;
import com.hqd.ch03.v35.core.convert.TypeDescriptor;
import com.hqd.ch03.v35.factory.ConfigurableBeanFactory;
import com.hqd.ch03.v35.utils.BeanUtils;
import com.hqd.ch03.v35.utils.StringUtils;
import com.hqd.ch03.v35.web.bind.annotation.RequestParam;
import com.hqd.ch03.v35.web.bind.annotation.ValueConstants;
import com.hqd.ch03.v35.web.context.request.NativeWebRequest;
import com.hqd.ch03.v35.web.servlet.mvc.method.annotation.AbstractNamedValueMethodArgumentResolver;
import com.hqd.ch03.v35.web.servlet.support.UriComponentsContributor;
import com.hqd.ch03.v35.web.utils.UriComponentsBuilder;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.Part;
import java.util.Collection;
import java.util.Map;
import java.util.Optional;

public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver
        implements UriComponentsContributor {

    private static final TypeDescriptor STRING_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(String.class);

    private final boolean useDefaultResolution;


    public RequestParamMethodArgumentResolver(boolean useDefaultResolution) {
        this.useDefaultResolution = useDefaultResolution;
    }

    public RequestParamMethodArgumentResolver(ConfigurableBeanFactory beanFactory,
                                              boolean useDefaultResolution) {

        super(beanFactory);
        this.useDefaultResolution = useDefaultResolution;
    }


    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        if (parameter.hasParameterAnnotation(RequestParam.class)) {
            /**
             * map类型
             */
            if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
                RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
                return (requestParam != null && StringUtils.hasText(requestParam.name()));
            } else {
                return true;
            }
        } else {
            /**
             * 是否是Optional
             */
            parameter = parameter.nestedIfOptional();
            if (this.useDefaultResolution) {
                return BeanUtils.isSimpleProperty(parameter.getNestedParameterType());
            } else {
                return false;
            }
        }
    }

    @Override
    protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
        /**
         * 存在注解则使用注解建立名称信息
         */
        RequestParam ann = parameter.getParameterAnnotation(RequestParam.class);
        return (ann != null ? new RequestParamNamedValueInfo(ann) : new RequestParamNamedValueInfo());
    }

    @Override
    protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
        HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
        Object arg = null;
        if (arg == null) {
            String[] paramValues = servletRequest.getParameterValues(name);
            if (paramValues != null) {
                arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
            }
        }
        return arg;
    }

    @Override
    protected void handleMissingValue(String name, MethodParameter parameter, NativeWebRequest request)
            throws Exception {

        handleMissingValueInternal(name, parameter, request, false);
    }

    @Override
    protected void handleMissingValueAfterConversion(
            String name, MethodParameter parameter, NativeWebRequest request) throws Exception {

        handleMissingValueInternal(name, parameter, request, true);
    }

    protected void handleMissingValueInternal(
            String name, MethodParameter parameter, NativeWebRequest request, boolean missingAfterConversion)
            throws Exception {

    }

    @Override
    public void contributeMethodArgument(MethodParameter parameter, Object value,
                                         UriComponentsBuilder builder, Map<String, Object> uriVariables, ConversionService conversionService) {

        Class<?> paramType = parameter.getNestedParameterType();
        if (Map.class.isAssignableFrom(paramType) || Part.class == paramType) {
            return;
        }

        RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
        String name = (requestParam != null && StringUtils.hasLength(requestParam.name()) ?
                requestParam.name() : parameter.getParameterName());

        parameter = parameter.nestedIfOptional();
        if (value instanceof Optional) {
            value = ((Optional<?>) value).orElse(null);
        }

        if (value == null) {
            if (requestParam != null &&
                    (!requestParam.required() || !requestParam.defaultValue().equals(ValueConstants.DEFAULT_NONE))) {
                return;
            }
            builder.queryParam(name);
        } else if (value instanceof Collection) {
            for (Object element : (Collection<?>) value) {
                element = formatUriValue(conversionService, TypeDescriptor.nested(parameter, 1), element);
                builder.queryParam(name, element);
            }
        } else {
            builder.queryParam(name, formatUriValue(conversionService, new TypeDescriptor(parameter), value));
        }
    }


    protected String formatUriValue(
            ConversionService cs, TypeDescriptor sourceType, Object value) {

        if (value == null) {
            return null;
        } else if (value instanceof String) {
            return (String) value;
        } else if (cs != null) {
            return (String) cs.convert(value, sourceType, STRING_TYPE_DESCRIPTOR);
        } else {
            return value.toString();
        }
    }


    private static class RequestParamNamedValueInfo extends NamedValueInfo {

        public RequestParamNamedValueInfo() {
            super("", false, ValueConstants.DEFAULT_NONE);
        }

        public RequestParamNamedValueInfo(RequestParam annotation) {
            super(annotation.name(), annotation.required(), annotation.defaultValue());
        }
    }

}
